<?php
/**
 * @link https://www.len168.com
 * @copyright Copyright (c) 2020/9/21 len168.com
 * @author toshcn <toshcn@foxmail.com>
 */

namespace common\models;

use Yii;
use yii\filters\RateLimitInterface;
use yii\web\IdentityInterface;

/**
 * This is the model class for table "{{%user}}".
 *
 * @property int $id
 * @property string $nickname 用户昵称
 * @property int $group_id 管理员组id
 * @property int $country_code 手机号国家代码
 * @property int $mobile 手机号
 * @property string $avatar 头像链接地址
 * @property string $password_hash 账号密码
 * @property string $password_salt 密码盐
 * @property string $access_token API登录令牌
 * @property int $access_token_expire API登录令牌有效时长
 * @property int $status 账号状态：0注销，9禁用，10激活
 * @property int $user_status 用户状态：0异常，1正常
 * @property int $login_type 登录方式: 0密码，1手机验证码
 * @property int $password_error 登录密码错误次数计数
 * @property int $parent_1 父级用户ID
 * @property object $userCount 统计表
 * @property object $userExtend 扩展表
 * @property string $invite_code 邀请注册码
 * @property string $login_ip
 * @property string $login_at
 * @property string $created_at
 * @property string $updated_at
 */
class User extends BaseActiveRecord implements IdentityInterface, RateLimitInterface
{
    const STATUS_DELETED = 0;  // 账号状态码 已删除
    const STATUS_INACTIVE = 9; // 账号状态码 待激活
    const STATUS_ACTIVE = 10;  // 账号状态码 正常
    const DEFAULT_LEVEL = 1; // 默认等级
    const LOGIN_TYPE_PWD = 1; // 密码登录
    const LOGIN_TYPE_CAPTCHA = 2; // 验证码登录
    const LOGIN_TYPE_WEIXIN = 3; // 微信登录
    const LOGIN_TYPE_ALIPAY = 4; // 支付宝登录
    const INVITE_STATUS_NO = 0; // 邀请状态 非邀请会员
    const INVITE_STATUS_ING = 1; // 邀请状态 邀请中刚注册
    const INVITE_STATUS_NEW = 2; // 邀请状态 完成新手任务

    /**
     * {@inheritdoc}
     */
    public static function tableName()
    {
        return '{{%user}}';
    }

    /**
     * {@inheritdoc}
     */
    public function behaviors()
    {
        return [];
    }

    /**
     * {@inheritdoc}
     */
    public function rules()
    {
        return [
            [['nickname', 'avatar'], 'trim'],
            ['country_code', 'default', 'value' => 86],
            [['nickname', 'password_hash', 'password_salt', 'access_token', 'created_at', 'updated_at'], 'required'],
            [['group_id', 'country_code', 'mobile', 'access_token_expire', 'status', 'user_status', 'login_type', 'parent_1', 'password_error'], 'integer'],
            [['invite_code', 'created_at', 'updated_at'], 'safe'],
            [['nickname', 'password_salt'], 'string', 'max' => 20],
            [['avatar'], 'string', 'max' => 128],
            [['password_hash'], 'string', 'max' => 72],
            [['access_token'], 'string', 'max' => 32],
            [['nickname'], 'unique'],
            [['access_token'], 'unique'],
            [['country_code', 'mobile', 'status'], 'unique', 'targetAttribute' => ['country_code', 'mobile', 'status']],
            ['status', 'default', 'value' => self::STATUS_ACTIVE],
            ['status', 'in', 'range' => [self::STATUS_ACTIVE, self::STATUS_INACTIVE, self::STATUS_DELETED]],
        ];
    }

    /**
     * 用户表扩展字段
     * @return \yii\db\ActiveQuery
     */
    public function getUserExtend()
    {
        return $this->hasOne(UserExtend::class, ['uid' => 'id']);
    }

    /**
     * 父级
     * @return \yii\db\ActiveQuery
     */
    public function getParentOne()
    {
        return $this->hasOne(User::class, ['id' => 'parent_1']);
    }

    /**
     * 登录成功后事件
     * @param $event
     * @return bool
     */
    public static function afterLoginHandler($event)
    {
        $user = $event->identity;
        $trans = Yii::$app->getDb()->beginTransaction();
        try {
            $isDay = date('Ymd') != date('Ymd', strtotime($user->login_at));
            if ($user->access_token_expire <= time()) {
                // 令牌失效 重新生成
                $user->generateAccessToken();
                $isDay = true;
            }
            if ($user->password_error && $user->login_type == static::LOGIN_TYPE_PWD) {
                $user->password_error = 0;
                $isDay = true;
            }
            if ($isDay) {
                // 生成邀请注册码
                $user->invite_code = static::encodeInviteCode($user->id);
                $user->login_ip = Yii::$app->getRequest()->getUserIP();
                $user->login_at = date('Y-m-d H:i:s');
                if ($user->update(false, ['invite_code', 'password_error','login_ip', 'login_at', 'login_type', 'access_token', 'access_token_expire'])) {
                    $trans->commit();
                    return true;
                }
            }
            $trans->rollBack();
        } catch (\Exception $e) {
            $trans->rollBack();
        }
        return false;
    }

    /**
     * {@inheritdoc}
     */
    public static function findIdentity($id)
    {
        return static::findOne(['id' => $id, 'status' => self::STATUS_ACTIVE]);
    }

    /**
     * {@inheritdoc}
     */
    public static function findIdentityByAccessToken($token, $type = null)
    {
        return static::find()->where(['and', ['access_token' => $token], ['status' => self::STATUS_ACTIVE], ['>', 'access_token_expire', time()]])->one();
    }

    /**
     * Finds user by username
     *
     * @param string $username
     * @return static|null
     */
    public static function findByUsername($username)
    {
        return static::findByNickname($username);
    }

    /**
     * Finds user by nickname
     *
     * @param string $nickname
     * @return static|null
     */
    public static function findByNickname($nickname)
    {
        return static::findOne(['nickname' => $nickname, 'status' => self::STATUS_ACTIVE]);
    }

    /**
     * Finds user by mobile
     *
     * @param integer $code
     * @param number $mobile
     * @return static|null
     */
    public static function findByMobile($code, $mobile)
    {
        return static::findOne(['country_code' => (int)$code, 'mobile' => $mobile, 'status' => self::STATUS_ACTIVE]);
    }

    /**
     * {@inheritdoc}
     */
    public function getId()
    {
        return $this->getPrimaryKey();
    }

    /**
     * {@inheritdoc}
     */
    public function getAuthKey()
    {
        return '';
    }

    /**
     * {@inheritdoc}
     */
    public function validateAuthKey($authKey)
    {
        return false;
    }

    /**
     * Returns the maximum number of allowed requests and the window size.
     * @param \yii\web\Request $request the current request
     * @param \yii\base\Action $action the action to be executed
     * @return array an array of two elements. The first element is the maximum number of allowed requests,
     * and the second element is the size of the window in seconds.
     */
    public function getRateLimit($request, $action)
    {
        // 表示在 5 秒内最多 200 次的 API 调用
        return [200, 5];
    }

    /**
     * Loads the number of allowed requests and the corresponding timestamp from a persistent storage.
     * @param \yii\web\Request $request the current request
     * @param \yii\base\Action $action the action to be executed
     * @return array an array of two elements. The first element is the number of allowed requests,
     * and the second element is the corresponding UNIX timestamp.
     */
    public function loadAllowance($request, $action)
    {
        $limit = (int)Yii::$app->getCache()->get(Yii::$app->util->cacheKey('rateLimiter_allowance_' . $this->id));
        $time = Yii::$app->getCache()->get(Yii::$app->util->cacheKey('rateLimiter_allowance_updated_at_' . $this->id));
        return [200 - $limit, $time ? $time : time()];
    }

    /**
     * Saves the number of allowed requests and the corresponding timestamp to a persistent storage.
     * @param \yii\web\Request $request the current request
     * @param \yii\base\Action $action the action to be executed
     * @param int $allowance the number of allowed requests remaining.
     * @param int $timestamp the current timestamp.
     */
    public function saveAllowance($request, $action, $allowance, $timestamp)
    {
        Yii::$app->getCache()->set(Yii::$app->util->cacheKey('rateLimiter_allowance_' . $this->id), $allowance);
        Yii::$app->getCache()->set(Yii::$app->util->cacheKey('rateLimiter_allowance_updated_at_' . $this->id), $timestamp);
    }

    /**
     * Validates password
     *
     * @param string $password password to validate
     * @return bool if password provided is valid for current user
     */
    public function validatePassword($password)
    {
        $password = sha1(md5($password) . md5($this->password_salt));
        return Yii::$app->getSecurity()->validatePassword($password, $this->password_hash);
    }

    /**
     * 登录密码错误 增加次数
     */
    public function loginPasswordError()
    {
        $this->password_error = new \yii\db\Expression('`password_error` + 1');
        $this->update(false, ['password_error']);
    }
    
    /**
     * 生成账号密码
     *
     * @param string $password
     * @throws \yii\base\Exception
     */
    public function setPassword($password)
    {
        $this->password_salt = Yii::$app->getSecurity()->generateRandomString(12);
        $this->password_hash = sha1(md5($password) . md5($this->password_salt));
        $this->password_hash = Yii::$app->getSecurity()->generatePasswordHash($this->password_hash);
    }

    /**
     * 生成Api登录认证token
     * Generates access token
     * @throws \yii\base\Exception
     */
    public function generateAccessToken()
    {
        $this->access_token = md5(sha1($this->id . '_' . Yii::$app->getSecurity()->generateRandomString(12)));
        $this->access_token_expire = time() + Yii::$app->config->getUserItem('userLoginAccessTokenExpire');
    }

    /**
     * 更新头像
     * @param string $img 头像
     * @return false|int
     * @throws \Throwable
     * @throws \yii\db\StaleObjectException
     */
    public function updateAvatar($img)
    {
        $this->avatar = $img;
        return $this->update(false, ['avatar']);
    }

    /**
     * 获取未登录用户信息
     * @return array
     */
    public function userInfo()
    {
        $data = [
            'uid' => (int)$this->id,
            'nickname' => Yii::$app->util->hideMobile($this->nickname),
            'avatar' => $this->avatar,
            'sex' => (int)$this->userExtend->sex,
        ];
        return $data;
    }

    /**
     * 获取邀请用户信息
     * @return array
     */
    public function inviteUserInfo()
    {
        $data = [
            'uid' => (int)$this->id,
            'nickname' => Yii::$app->util->hideMobile($this->nickname),
            'avatar' => $this->avatar
        ];
        return $data;
    }

    /**
     * 获取当前登录用户信息
     * @return array
     * @throws \Throwable
     */
    public function getCurrentUserInfo()
    {
        return [
            'uid' => (int)$this->id,
            'nickname' => Yii::$app->util->hideMobile($this->nickname),
            'mobile' => Yii::$app->util->hideMobile($this->mobile),
            'country_code' => (int)$this->country_code,
            'avatar' => $this->avatar,
            'token' => 'Bearer ' . $this->access_token,
            'expire' => 'Bearer ' . $this->access_token_expire,
            'invite_code' => $this->invite_code
        ];
    }

    /**
     * 获取当前登录管理员用户信息
     * @return array
     */
    public function getCurrentAdminInfo()
    {
        //用户角色
        $roles = Yii::$app->getAuthManager()->getRolesByUser($this->id);
        $arr = [];
        foreach ($roles as $role) {
            $arr[] = ['name' => $role->name, 'description' => $role->description];
        }
        return [
            'uid' => $this->id,
            'group_id' => $this->group_id,
            'nickname' => Yii::$app->util->hideMobile($this->nickname),
            'token' => 'Bearer ' . $this->access_token,
            'avatar' => $this->avatar,
            'expire' => $this->access_token_expire,
            'sex' => (int)$this->userExtend->sex,
            'roles' => $arr
        ];
    }

    /**
     * 后台 获取用户信息
     * @param int $uid 用户id
     * @return array
     */
    public static function getUserDetailOnBackend($uid)
    {
        $user = static::findOne($uid);
        return [
            'uid' => $user->id,
            'group_id' => $user->group_id,
            'nickname' => $user->nickname,
            'avatar' => $user->avatar,
            'sex' => (int) $user->userExtend->sex,
            'login_ip' => $user->login_ip,
            'login_at' => $user->login_at,
            'login_type' => $user->login_type,
            'created_at' => $user->created_at,
            'userExtend' => [
                'introduce' => $user->userExtend->introduce
            ]
        ];
    }

    /**
     * 重置密码
     * @param string $captcha 验证码
     * @param string $password 密码
     * @return bool
     * @throws \yii\base\Exception
     */
    public function resetPassword($captcha, $password)
    {
        if (empty($captcha)) {
            $this->addError('password', '验证码不能为空');
            return false;
        }
        if (empty($password)) {
            $this->addError('password', '密码不能为空');
            return false;
        }
        if (Yii::$app->sms->validateCaptcha($captcha, $this->country_code . $this->mobile, SmsCaptchaForm::LABEL_RESET_PWD) == false) {
            $this->addError('password', '验证码错误');
            return false;
        }
        if (!Yii::$app->util->regExp($password, 'password_power')) {
            $this->addError('密码必需包含大小写字母和数字的组合，长度在8-20');
            return false;
        }
        $this->setPassword(sha1($password));
        $this->updated_at = date('Y-m-d H:i:s');
        return $this->save(false);
    }

    /**
     * 邀请码加密
     * @param integer $uid 用户ID
     * @return string
     */
    public static function encodeInviteCode($uid)
    {
        $str = base64_encode(mt_rand(9, 9999) . '_' . $uid . '_' . time());
        $len = strlen($str);

        $one = [];
        $two = [];
        if ($len % 2 === 0) {
            //偶数 把偶数位抽出排前面
            for ($i = 0; $i < $len; $i++) {
                if ($i % 2 === 0) {
                    $one[] = $str[$i];
                } else {
                    $two[] = $str[$i];
                }
            }
        } else {
            //奇数 把奇数位抽出排前面
            for ($i = 0; $i < $len; $i++) {
                if ($i % 2 === 0) {
                    $two[] = $str[$i];
                } else {
                    $one[] = $str[$i];
                }
            }
        }
        return join('', $one) . join('', $two);
    }

    /**
     * 邀请码解密
     * @param string $str 加密字符串
     * @return string
     */
    public static function decodeInviteCode($str)
    {
        $len = strlen($str);
        $old = [];
        if ($len % 2 === 0) {
            //偶数
            $half = $len / 2;
            $one = substr($str, 0, $half);// 偶数位
            $two = substr($str, $half); // 奇数位
            for ($i = 0; $i < $half; $i++) {
                $old[] = $one[$i];
                $old[] = $two[$i];
            }
            $str = join('', $old);
        } else {
            //奇数
            $half = ($len + 1) / 2;
            $one = substr($str, 0, $half - 2); // 奇数位
            $oneLen = strlen($one);
            $two = substr($str, $half - 1); // 偶数位
            for ($i = 0; $i < strlen($two); $i++) {
                $old[] = $two[$i];
                if ($i < $oneLen) {
                    $old[] = $one[$i];
                }
            }
            $str = join('', $old);
        }
        return base64_decode($str);
    }

}
